<!DOCTYPE html>
<html lang="en">
<head>
    <title>半透明加漫反射</title>
    <meta charset="utf-8">

    <script id="translucency-vert" type="x-shader/x-vertex">
			void main() {
			    gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
			}
	</script>

    <script id="translucency-frag" type="x-shader/x-fragment">
            precision mediump float;

            vec4 packDepth(float depth){
                vec4 bitShift = vec4(1.0, 256.0, 256.0 * 256.0, 256.0 * 256.0 * 256.0);
                vec4 bitMask = vec4(1.0/256.0, 1.0/256.0, 1.0/256.0, 0.0);
                vec4 rgbaDepth = fract(depth * bitShift);
                rgbaDepth -= rgbaDepth.gbaa * bitMask;
                return rgbaDepth;
            }

            void main(){
                gl_FragColor = packDepth(gl_FragCoord.z);
            }
    </script>

    <script id="lambertDiffuse-vert" type="x-shader/x-vertex">
            varying   vec3 vPosition;
            varying   vec3 vNormal;
            varying   vec4 vColor;

            void main(void){
                vPosition = (modelMatrix * vec4(position, 1.0)).xyz;
                vNormal = normal;
                vColor = vec4(0.9, 0.6, 0.1, 1.0);
                gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
            }
    </script>

    <script id="lambertDiffuse-frag" type="x-shader/x-fragment">
            precision mediump float;

            uniform mat4 invMatrix;
            uniform vec3 lightPosition1;
            uniform vec3 lightPosition2;
            uniform vec3 lightPosition3;
            uniform vec3 lightPosition4;
            uniform vec4 ambientColor;
            varying vec3 vPosition;
            varying vec3 vNormal;
            varying vec4 vColor;

            float diffuse(mat4 invMat, vec3 lightPos, vec3 pos, vec3 normal){
                vec3  lightVec  = lightPos - pos;
                vec3  invLight  = normalize(invMat * vec4(lightVec, 0.0)).xyz;
                float diffuse   = clamp(dot(normal, invLight), 0.0, 1.0) + 0.3;
                return diffuse;
            }

            float specular(mat4 invMat, vec3 lightPos, vec3 cameraPos, vec3 pos, vec3 normal){
                vec3  lightVec  = lightPos - pos;
                vec3  invLight  = normalize(invMat * vec4(lightVec, 0.0)).xyz;
                vec3  invEye    = normalize(invMat * vec4(cameraPos, 0.0)).xyz;
                vec3  halfLE    = normalize(invLight + invEye);
                float specular  = pow(clamp(dot(normal, halfLE), 0.0, 1.0), 900.0);
                return specular;
            }

            void main(void){
                //第一个点光源的漫反射
                float diffuse1 = diffuse(invMatrix, lightPosition1, vPosition, vNormal);
                //第一个点光源的全反射
                float specular1 = specular(invMatrix, lightPosition1, cameraPosition, vPosition, vNormal);
                //第二个点光源的漫反射
                float diffuse2 = diffuse(invMatrix, lightPosition2, vPosition, vNormal);
                //第二个点光源的全反射
                float specular2 = specular(invMatrix, lightPosition2, cameraPosition, vPosition, vNormal);
                //第三个点光源的漫反射
                float diffuse3 = diffuse(invMatrix, lightPosition3, vPosition, vNormal);
                //第三个点光源的全反射
                float specular3 = specular(invMatrix, lightPosition3, cameraPosition, vPosition, vNormal);
                //第四个点光源的漫反射
                float diffuse4 = diffuse(invMatrix, lightPosition4, vPosition, vNormal);
                //第四个点光源的全反射
                float specular4 = specular(invMatrix, lightPosition4, cameraPosition, vPosition, vNormal);

                float diffuse = max(max(diffuse1, diffuse2), max(diffuse3, diffuse4));
                float specular = max(max(specular1, specular2), max(specular3, specular4));

                //混合所有光源的diffuse
                vec4 diffuseColor = vColor * vec4(vec3(diffuse), 1.0);
                //混合所有光源的specular
                vec4 specularColor = vec4(vec3(specular)*0.19, 1.0);
                //着色
                gl_FragColor = diffuseColor + specularColor + ambientColor;
            }
    </script>

    <script id="post-vert" type="x-shader/x-vertex">
			varying vec2 vUv;
			varying vec4 vColor;
			void main() {
			    vColor = vec4(1.0, 1.0, 1.0, 1.0);
				vUv = uv;
				gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
			}
	</script>

    <script id="post-frag" type="x-shader/x-fragment">
            precision mediump float;

            varying vec4 vColor;
			varying vec2 vUv;
			uniform sampler2D tDepthFront;
			uniform sampler2D tDepthBack;
			uniform float cameraNear;
			uniform float cameraFar;
			uniform vec4 ambientLight;

            float perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {
                return ( near * far ) / ( ( far - near ) * invClipZ - far );
            }

            float viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {
                return ( viewZ + near ) / ( near - far );
            }

            float unpackDepth(const in vec4 rgbaDepth) {
                const vec4 bitShift = vec4(1.0, 1.0/255.0, 1.0/(255.0 * 255.0), 1.0/(255.0*255.0*255.0));
                float depth = dot(rgbaDepth, bitShift);
                return depth;
            }

			float readDepth( sampler2D depthSampler, vec2 coord ) {
				float fragCoordZ = unpackDepth(texture2D( depthSampler, coord ));
				float viewZ = perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );
				return viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
			}

			void main() {
				float depthFront = readDepth( tDepthFront, vUv );
				float depthBack = readDepth( tDepthBack, vUv );
				float thickness = depthBack - depthFront;
				float translucencyRatio = 1.0 - pow((thickness - 1.0), 4.0) + 0.9;
				vec4 fColor = ambientLight;

				if(thickness < 0.0001){
				    fColor = ambientLight;
				}else if(thickness > 0.0001){
				    fColor = ambientLight * (1.0 - translucencyRatio) + vColor * translucencyRatio;
				}

				gl_FragColor = fColor;
			}
	</script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>

    <script src="./js/TrackballControls.js"></script>

</head>
<body>
<canvas></canvas>
<script type="module">
    let camera, scene, renderer, controls;
    let targetFront, targetBack;
    let postScene, postCamera;
    let supportsExtension = true;

    init();
    animate();

    function init() {

        renderer = new THREE.WebGLRenderer( {
            canvas: document.querySelector( 'canvas' ),
            antialias:true
        } );

        if ( ! renderer.extensions.get( 'WEBGL_depth_texture' ) ) {

            supportsExtension = false;
            document.querySelector( '#error' ).style.display = 'block';
            return;

        }

        renderer.setPixelRatio( window.devicePixelRatio );
        renderer.setSize( window.innerWidth, window.innerHeight );

        camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 1000);
        camera.position.z = 500;

        controls = new THREE.TrackballControls(camera, renderer.domElement);
        controls.rotateSpeed = 1.6;// 旋转速度
        controls.zoomSpeed = 1.6;// 缩放速度
        controls.panSpeed = 0.6;// 平controls
        controls.staticMoving = true;// 静止移动，为 true 则没有惯性
        controls.dynamicDampingFactor = 0.2;// 阻尼系数 越小 则滑动越大
        controls.minDistance = 5; // 最小视角
        controls.maxDistance = 1000;// 最大视角 Infinity 无穷大

        // Create a multi render target with Float buffers
        targetFront = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight );
        targetBack = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight );

        scene = new THREE.Scene();
    }

    function setupScene(frontOrBack) {
        //clear
        if(scene.children.length !== 0){
            scene.children[0].geometry.dispose();
            scene.children[0].material.dispose();
            scene.remove(scene.children[0]);
        }
        //add new
        let geometry = new THREE.CubeGeometry(100, 100, 100, 10, 10, 10);
        let material = new THREE.ShaderMaterial({
            vertexShader: document.querySelector( '#translucency-vert' ).textContent.trim(),
            fragmentShader: document.querySelector( '#translucency-frag' ).textContent.trim()
        });
        if(frontOrBack === "front"){
            material.side = THREE.FrontSide;
        }else if(frontOrBack === "back"){
            material.side = THREE.BackSide;
        }
        let mesh = new THREE.Mesh( geometry, material );
        scene.add( mesh );
    }

    function setupPost() {
        // Setup post processing stage
        postCamera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
        let postMaterial = new THREE.ShaderMaterial( {
            vertexShader: document.querySelector( '#post-vert' ).textContent.trim(),
            fragmentShader: document.querySelector( '#post-frag' ).textContent.trim(),
            uniforms: {
                cameraNear: { value: camera.near },
                cameraFar: { value: camera.far },
                tDepthFront: { value: targetFront.texture },
                tDepthBack: { value: targetBack.texture },
                ambientLight: {value: [1.0, 0.0, 0.0, 1.0]}
            }
        } );
        let postPlane = new THREE.PlaneBufferGeometry( 2, 2 );
        let postQuad = new THREE.Mesh( postPlane, postMaterial );
        postScene = new THREE.Scene();
        postScene.add( postQuad );
    }

    function animate() {
        if ( ! supportsExtension ) return;
        requestAnimationFrame( animate );
        controls.update();
        //前表面深度图
        renderer.setRenderTarget( targetFront );
        setupScene("front");
        renderer.render( scene, camera );
        //后表面深度图
        renderer.setRenderTarget( targetBack );
        setupScene("back");
        renderer.render( scene, camera );
        // render post FX
        renderer.setRenderTarget( null );
        setupPost();
        renderer.render( postScene, postCamera );
    }
</script>
</body>
</html>